﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Threading;
using RPGX.RPGXAuthentication;
using RPGXEngine;
using System.Net.Sockets;
using System.Net;
using RPGXEngine.Networking;
using RPGXEngine.Factories;
using System.Diagnostics;

namespace RPGX.Network
{
    public class NetworkManager
    {
        #region Enums

        public enum States
        {
            Idle,
            Authenticating,
            ConnectingToServer,
            ConnectedSendWelcome,
            ConnectedRunning,
        }

        #endregion

        #region Thread Start Objects

        private struct AuthenticateThreadStartObject
        {
            public string UserName;
            public string Password;
        }

        #endregion

        #region Properties

        public static object Lock = new object();

        public ILogger Logger
        {
            get { return _logger; }
            set { _logger = value; }
        }
        private ILogger _logger = null;

        public States State
        {
            get { return _state; }
            set { _state = value; }
        }
        private States _state = States.Idle;

        public AuthenticationResult AuthenticationResult
        {
            get { return _authenticationResult; }
            set { _authenticationResult = value; }
        }
        private AuthenticationResult _authenticationResult = null;

        public string AuthenticationErrorCallBack
        {
            get { return _authenticationErrorCallBack; }
            set { _authenticationErrorCallBack = value; }
        }
        private string _authenticationErrorCallBack = String.Empty;

        public string AuthenticationSuccessCallBack
        {
            get { return _authenticationSuccessCallBack; }
            set { _authenticationSuccessCallBack = value; }
        }
        private string _authenticationSuccessCallBack = String.Empty;

        public string ServerAddress
        {
            get { return _serverAddress; }
            set { _serverAddress = value; }
        }
        private string _serverAddress = String.Empty;

        public int ServerPort
        {
            get { return _serverPort; }
            set { _serverPort = value; }
        }
        private int _serverPort = 0;

        public TcpClient TcpClient
        {
            get { return _tcpClient; }
            set { _tcpClient = value; }
        }
        private TcpClient _tcpClient = null;

        public NetworkStream OutputTcpStream
        {
            get { return _outputTcpStream; }
            set { _outputTcpStream = value; }
        }
        private NetworkStream _outputTcpStream = null;

        #endregion

        #region Public Methods

        public NetworkManager()
        {
            this.Logger = LoggerFactory.Get();
        }

        public void Authenticate(string username, string password, string scriptErrorCallBack, string scriptSuccessCallBack)
        {
            Thread t = new Thread(this.AuthenticateThreadWorker);

            AuthenticateThreadStartObject o = new AuthenticateThreadStartObject();
            o.UserName = username;
            o.Password = password;

            this.AuthenticationErrorCallBack = scriptErrorCallBack;
            this.AuthenticationSuccessCallBack = scriptSuccessCallBack;

            t.Start(o);

            this.State = States.Authenticating;
        }

        public void Update(GameTime time)
        {
            if (this.State == States.Idle)
                return;

            if (this.State == States.Authenticating)
            {
                this.CheckAuthentication();
            }
            else if (this.State == States.ConnectingToServer)
            {
                this.ConnectToServer();
            }
            else if (this.State == States.ConnectedSendWelcome)
            {
                this.SendWelcomeMessage();
            }
            else if (this.State == States.ConnectedRunning)
            {
            }
        }

        public void UnLoad()
        {
            if (this.OutputTcpStream != null)
            {
                this.OutputTcpStream.Close();

                this.OutputTcpStream = null;
            }

            if (this.TcpClient != null)
            {
                this.Logger.Log("Attempting to disconnect from server...");
                this.TcpClient.Close();
                this.Logger.Log("Disconnected from server.");

                this.TcpClient = null;
            }
        }

        #endregion

        #region Private Methods

        private void ConnectToServer()
        {
            this.Logger.Log(String.Format("Attempting to connect to server: {0}:{1}", this.ServerAddress, this.ServerPort));

            this.TcpClient = new TcpClient();

            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(this.ServerAddress), this.ServerPort);

            this.TcpClient.Connect(endPoint);

            this.Logger.Log("Connected to server.");

            Game1.Instance.ScriptManager.RunCode(this.AuthenticationSuccessCallBack);

            this.State = States.ConnectedSendWelcome;

            this.OutputTcpStream = this.TcpClient.GetStream();

            MessageReader reader = new MessageReader();
            reader.ProcessMessage += new MessageReader.ProcessMessageDelegate(reader_ProcessMessage);

            reader.Read(this.OutputTcpStream);
        }

        private void CheckAuthentication()
        {
            bool error = false;

            lock (NetworkManager.Lock)
            {
                if (this.AuthenticationResult == null)
                    return;

                // connect
                error = !this.AuthenticationResult.Authenticated || !this.AuthenticationResult.CouldConnectToServer;
                if (!error)
                {
                    this.ServerAddress = this.AuthenticationResult.ServerAddress;
                    this.ServerPort = this.AuthenticationResult.ServerPort;

                    Game1.Instance.GameClient.ClientAccountId = this.AuthenticationResult.ClientAccountId;

                    this.Logger.Log(String.Format("Authenticated user with ClientAccountId: {0}",
                        this.AuthenticationResult.ClientAccountId));

                    this.State = States.ConnectingToServer;
                }
            }

            // now we are out of the lock, report the error if there is one
            if (error)
            {
                Game1.Instance.ScriptManager.RunCode(this.AuthenticationErrorCallBack);

                this.State = States.Idle;
            }
        }

        #endregion

        #region Thread Methods

        private void AuthenticateThreadWorker(object data)
        {
            do
            {
                try
                {
                    AuthenticateThreadStartObject o = (AuthenticateThreadStartObject)data;

                    RPGXAuthentication.AuthenticationSoapClient c = new RPGXAuthentication.AuthenticationSoapClient();
                    var res = c.Authenticate(o.UserName, o.Password);

                    lock (NetworkManager.Lock)
                    {
                        this.AuthenticationResult = res;
                    }
                }
                catch (TimeoutException ex)
                {
                    Trace.Write(ex.Message);

                    // retry the authentication - maybe the web service hasn't started yet
                    continue;
                }
            } while (false); // only try successfully once
        }

        private void reader_ProcessMessage(NetworkMessage message)
        {
            NetworkMessageProcessor.Process(message);
        }

        private void SendWelcomeMessage()
        {
            NetworkMessage_ConnectToServer m = new NetworkMessage_ConnectToServer();
            m.ClientAccountId = Game1.Instance.GameClient.ClientAccountId;

            this.SendMessage(m);

            this.State = States.ConnectedRunning;
        }

        private void SendMessage(NetworkMessage message)
        {
            byte[] bytes = message.GetMessageAsBytes();

            this.OutputTcpStream.Write(bytes, 0, bytes.Length);
            this.OutputTcpStream.Flush();
        }

        #endregion
    }
}
